package org.mozhu.mboot.core.web.controller;

import com.google.common.collect.Lists;
import org.mozhu.mboot.core.data.SearchFilters;
import org.mozhu.mboot.core.data.Searchable;
import org.mozhu.mboot.core.data.entity.IEntity;
import org.mozhu.mboot.core.data.entity.Treeable;
import org.mozhu.mboot.core.data.filter.Operator;
import org.mozhu.mboot.core.data.filter.SearchFilter;
import org.mozhu.mboot.core.data.support.IBaseTreeableService;
import org.mozhu.mboot.core.data.support.ZTree;
import org.mozhu.mboot.core.json.JSON;
import org.mozhu.mboot.core.security.Permissions;
import org.mozhu.mboot.core.support.datatable.DTReturnObject;
import org.mozhu.mboot.core.support.datatable.DTUtils;
import org.mozhu.mboot.core.util.ReflectionUtils;
import org.mozhu.mboot.core.web.Constants;
import org.mozhu.mboot.core.web.bind.annotation.PageableDefaults;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.Set;

public class BaseTreeableController<M extends IEntity<ID> & Treeable<ID>, ID extends Serializable> extends BaseController implements ConfigurableController {

	public final static String OPERATION_LIST_CHILDREN = "op.list.children";
	public final static String OPERATION_ADD_CHILD = "op.add.child";
	public final static String OPERATION_MOVE = "op.move";

	public final static String ADD_CHILD_SUCCESS = "op.add.child.success";
	public final static String MOVE_SUCCESS = "op.move.success";
	protected IBaseTreeableService<M, ID> baseTreeableService;

	protected boolean listAlsoSetCommonData = true;

	// 公共视图main/tree/moveForm/success会使用该路径
	protected String commonViewPrefix = "common/component/tree";

	protected String entityName;

	protected Permissions permissions = null;
	/**
	 * 实体类型
	 */
	protected final Class<M> entityClass;

	public BaseTreeableController() {
		this.entityClass = ReflectionUtils.findParameterizedType(getClass(), 0);
		this.entityName = this.entityClass.getSimpleName().toLowerCase();
		setViewPrefix(defaultViewPrefix());
	}

	/**
	 * 权限前缀：如system:user 则生成的新增权限为 sytem:user:create
	 */
	@Override
    public void setResourceIdentity(String resourceIdentity) {
		if (!StringUtils.isEmpty(resourceIdentity)) {
			permissions = Permissions.newPermissions(resourceIdentity);
		}
	}

	protected M newModel() {
		try {
			return entityClass.newInstance();
		} catch (Exception e) {
			throw new IllegalStateException("can not instantiated model : " + this.entityClass, e);
		}
	}

	@Autowired
	public void setBaseTreeableService(IBaseTreeableService<M, ID> baseTreeableService) {
		this.baseTreeableService = baseTreeableService;
	}

	@RequestMapping(value = { "", "main" }, method = RequestMethod.GET)
	public String main(Model model) {
		if (permissions != null) {
			permissions.assertHasReadPermission();
		}
		setCommonData(model);
		return commonViewName("main");
	}

	protected String commonViewName(String viewName) {
		return this.commonViewPrefix + "/" + viewName;
	}

	@RequestMapping(value = "/{id}", method = RequestMethod.GET)
	public String view(@PathVariable("id") M m, Model model) {
		if (permissions != null) {
			permissions.assertHasReadPermission();
		}

		setCommonData(model);
		model.addAttribute("m", m);
		model.addAttribute(Constants.OP_NAME, OPERATION_VIEW);
		return viewName("editForm");
	}

	@RequestMapping(value = "create", method = RequestMethod.GET)
	public String createForm(Model model, RedirectAttributes redirectAttributes) {
		if (permissions != null) {
			permissions.assertHasUpdatePermission();
		}
		setCommonData(model);
		if (!model.containsAttribute("m")) {
			model.addAttribute("m", newModel());
		}
		model.addAttribute(Constants.OP_NAME, OPERATION_ADD);
		return viewName("editForm");
	}

	@RequestMapping(value = "create", method = RequestMethod.POST)
	public String create(HttpServletRequest request, Model model, @ModelAttribute("m") M m, BindingResult result, RedirectAttributes redirectAttributes) {
		if (permissions != null) {
			permissions.assertHasUpdatePermission();
		}
		baseTreeableService.save(m);
		redirectAttributes.addFlashAttribute(Constants.MESSAGE, CREATE_SUCCESS);
		return redirect("success");
	}

	@RequestMapping(value = "{id}/update", method = RequestMethod.GET)
	public String updateForm(@PathVariable("id") M m, Model model, RedirectAttributes redirectAttributes) {
		if (permissions != null) {
			permissions.assertHasUpdatePermission();
		}
		if (m == null) {
			redirectAttributes.addFlashAttribute(Constants.ERROR, "您修改的数据不存在！");
			return redirect("success");
		}
		setCommonData(model);
		model.addAttribute("m", m);
		model.addAttribute(Constants.OP_NAME, OPERATION_EDIT);
		return viewName("editForm");
	}

	@RequestMapping(value = "{id}/update", method = RequestMethod.POST)
	public String update(Model model, @ModelAttribute("m") M m, BindingResult result, RedirectAttributes redirectAttributes) {
		if (permissions != null) {
			permissions.assertHasUpdatePermission();
		}
		if (result.hasErrors()) {
			return updateForm(m, model, redirectAttributes);
		}
		baseTreeableService.save(m);
		redirectAttributes.addFlashAttribute(Constants.MESSAGE, UPDATE_SUCCESS);
		return redirect("success");
	}

	@RequestMapping(value = "{id}/delete", method = RequestMethod.GET)
	public String deleteForm(@PathVariable("id") M m, Model model) {
		if (permissions != null) {
			permissions.assertHasDeletePermission();
		}
		setCommonData(model);
		model.addAttribute("m", m);
		model.addAttribute(Constants.OP_NAME, OPERATION_DELETE);
		return viewName("editForm");
	}

	@RequestMapping(value = "{id}/delete", method = RequestMethod.POST)
	public String deleteSelfAndChildren(Model model, @ModelAttribute("m") M m, BindingResult result, RedirectAttributes redirectAttributes) {
		if (permissions != null) {
			permissions.assertHasDeletePermission();
		}

		if (m.isRoot()) {
			model.addAttribute(Constants.ERROR, "您删除的数据中包含根节点，根节点不能删除");
			return deleteForm(m, model);
		}

		baseTreeableService.deleteSelfAndChild(m);
		redirectAttributes.addFlashAttribute(Constants.MESSAGE, DELETE_SUCCESS);
		return redirect("success");
	}

	@RequestMapping(value = "batch/delete")
	public String deleteInBatch(HttpServletRequest request, @RequestParam(value = "ids", required = false) ID[] ids, @RequestParam(value = Constants.BACK_URL, required = false) String backURL,
                                RedirectAttributes redirectAttributes) {
		if (permissions != null) {
			permissions.assertHasDeletePermission();
		}

		// 如果要求不严格 此处可以删除判断 前台已经判断过了
		List<SearchFilter> searchFilters = SearchFilters.parse(request);
		searchFilters.add(SearchFilters.createFilter("id", Operator.IN, ids));
		List<M> mList = baseTreeableService.findAll(searchFilters);
		for (M m : mList) {
			if (m.isRoot()) {
				redirectAttributes.addFlashAttribute(Constants.ERROR, "您删除的数据中包含根节点，根节点不能删除");
				return redirect(backURL);
			}
		}

		baseTreeableService.deleteSelfAndChild(mList);
		redirectAttributes.addFlashAttribute(Constants.MESSAGE, DELETE_SUCCESS);
		return redirect(backURL);
	}

	@RequestMapping(value = "{parent}/appendChild", method = RequestMethod.GET)
	public String appendChildForm(@PathVariable("parent") M parent, Model model) {
		if (permissions != null) {
			permissions.assertHasCreatePermission();
		}
		setCommonData(model);
		if (!model.containsAttribute("child")) {
			model.addAttribute("child", newModel());
		}

		model.addAttribute(Constants.OP_NAME, OPERATION_ADD_CHILD);
		model.addAttribute("m", parent);
		return viewName("appendChildForm");
	}

	@RequestMapping(value = "{parent}/appendChild", method = RequestMethod.POST)
	public String appendChild(Model model, @PathVariable("parent") M parent, @ModelAttribute("child") M child, BindingResult result, RedirectAttributes redirectAttributes) {
		if (permissions != null) {
			permissions.assertHasCreatePermission();
		}
		setCommonData(model);

		if (result.hasErrors()) {
			return appendChildForm(parent, model);
		}

		baseTreeableService.appendChild(parent, child);

		redirectAttributes.addFlashAttribute(Constants.MESSAGE, ADD_CHILD_SUCCESS);
		return redirect("success");
	}

	@RequestMapping(value = "{source}/move", method = RequestMethod.GET)
	@PageableDefaults(sort = { "parentIds=asc", "weight=asc" })
	public String showMoveForm(HttpServletRequest request, @RequestParam(value = "async", required = false, defaultValue = "false") boolean async, @PathVariable("source") M source, Model model) {
		if (this.permissions != null) {
			this.permissions.assertHasEditPermission();
		}
		setCommonData(model);
		List<M> models = null;

		List<SearchFilter> searchFilters = SearchFilters.parse(request);
		// 排除自己及子子孙孙
		searchFilters.add(SearchFilters.createFilter("id", Operator.NE, source.getId()));
		searchFilters.add(SearchFilters.createFilter("parentIds", Operator.NPLIKE, source.makeSelfAsNewParentIds()));

		if (!async) {
			models = baseTreeableService.findAllWithSort(searchFilters);
		} else {
			models = baseTreeableService.findRootAndChild(searchFilters);
		}

		model.addAttribute("trees", baseTreeableService.convertToZtreeList(models, async, true));

		model.addAttribute(Constants.OP_NAME, OPERATION_MOVE);
		model.addAttribute("m", source);
		return commonViewName("moveForm");
	}

	@RequestMapping(value = "{source}/move", method = RequestMethod.POST)
	public String move(HttpServletRequest request, @RequestParam(value = "async", required = false, defaultValue = "false") boolean async, @PathVariable("source") M source,
					   @RequestParam("target") M target, @RequestParam("moveType") String moveType, Model model, RedirectAttributes redirectAttributes) {
		if (this.permissions != null) {
			this.permissions.assertHasEditPermission();
		}
		if (target.isRoot() && !"inner".equals(moveType)) {
			model.addAttribute(Constants.ERROR, "不能移动到根节点之前或之后");
			return showMoveForm(request, async, source, model);
		}
		baseTreeableService.move(source, target, moveType);
		redirectAttributes.addFlashAttribute(Constants.MESSAGE, MOVE_SUCCESS);
		return redirect("success");
	}

	@RequestMapping(value = "tree", method = RequestMethod.GET)
	public String tree(HttpServletRequest request, @RequestParam(value = "searchName", required = false) String searchName,
                       @RequestParam(value = "async", required = false, defaultValue = "false") boolean async, Model model) {
		if (permissions != null) {
			permissions.assertHasReadPermission();
		}
		setCommonData(model);
		List<M> models;
		List<SearchFilter> searchFilters = SearchFilters.parse(request);
		if (!StringUtils.isEmpty(searchName)) {
			searchFilters.add(SearchFilters.createFilter("name", Operator.LIKE, searchName));
			models = baseTreeableService.findAllByName(searchFilters, null);
			if (!async) { // 非异步 查自己和子子孙孙
				// remove name like filter
				searchFilters.remove(searchFilters.size() - 1);
				List<M> children = baseTreeableService.findChildren(models, searchFilters);
				models.removeAll(children);
				models.addAll(children);
			} else { // 异步模式只查自己

			}
		} else {
			if (!async) { // 非异步 查自己和子子孙孙
				models = baseTreeableService.findAllWithSort(searchFilters);
			} else { // 异步模式只查根 和 根一级节点
				models = baseTreeableService.findRootAndChild(searchFilters);
			}
		}

		models = baseTreeableService.sort(models);
		List<ZTree<ID>> convertToZtreeList = baseTreeableService.convertToZtreeList(models, async, true);
		model.addAttribute("trees", convertToZtreeList);
		model.addAttribute("treesJson", JSON.toJson(convertToZtreeList));

		return commonViewName("tree");
	}

	@RequestMapping(value = "{parent}/children", method = RequestMethod.GET)
	public String list(HttpServletRequest request, @PathVariable("parent") M parent, Model model) throws UnsupportedEncodingException {
		if (permissions != null) {
			permissions.assertHasReadPermission();
		}
		if (listAlsoSetCommonData) {
			setCommonData(model);
		}
		model.addAttribute("parent", parent);
		model.addAttribute(Constants.OP_NAME, OPERATION_LIST_CHILDREN);
		return viewName("listChildren");
	}

	@RequestMapping(value = "{parent}/children", headers = "ajax=true", method = RequestMethod.POST)
	@ResponseBody
	@PageableDefaults(sort = { "weight=asc" })
	public Object list(HttpServletRequest request, Model model, @PathVariable("parent") M parent, Searchable searchable) {
		if (permissions != null) {
			permissions.assertHasReadPermission();
		}
		searchable.and(SearchFilters.createFilter("parentId", Operator.EQ, parent.getId()));
		Page<M> page = baseTreeableService.findAll(searchable);
		DTReturnObject<M> dtReturnObject = DTUtils.toDTReturnObject(page);
		dtReturnObject.setDraw(request.getParameter("draw"));
		return dtReturnObject;
	}

	// ///////////////////////////////////ajax///////////////////////////////////////////////

	@RequestMapping(value = "ajax/load")
	@PageableDefaults(sort = { "parentIds=asc", "weight=asc" })
	@ResponseBody
	public Object load(HttpServletRequest request, @RequestParam(value = "async", defaultValue = "true") boolean async,
                       @RequestParam(value = "asyncLoadAll", defaultValue = "false") boolean asyncLoadAll, @RequestParam(value = "searchName", required = false) String searchName,
                       @RequestParam(value = "id", required = false) ID parentId, @RequestParam(value = "excludeId", required = false) ID excludeId,
                       @RequestParam(value = "onlyCheckLeaf", required = false, defaultValue = "false") boolean onlyCheckLeaf, Searchable searchable) {
		M excludeM = baseTreeableService.findById(excludeId);

		List<M> models;
		List<SearchFilter> searchFilters = Lists.newArrayList(searchable.getSearchFilters());

		if (!StringUtils.isEmpty(searchName)) {// 按name模糊查
			searchFilters.add(SearchFilters.createFilter("name", Operator.LIKE, searchName));
			models = baseTreeableService.findAllByName(searchFilters, excludeM);
			if (!async || asyncLoadAll) {// 非异步模式 查自己及子子孙孙 但排除
				// remove name like filter
				searchFilters.remove(searchFilters.size() - 1);
				List<M> children = baseTreeableService.findChildren(models, searchFilters);
				models.removeAll(children);
				models.addAll(children);
			} else { // 异步模式 只查匹配的一级

			}
		} else { // 根据有没有parentId加载

			if (parentId != null) { // 只查某个节点下的 异步
				searchFilters.add(SearchFilters.createFilter("parentId", Operator.EQ, parentId));
			}

			if (async && !asyncLoadAll) { // 异步模式下 且非异步加载所有
				// 排除自己 及 子子孙孙
				searchFilters.addAll(baseTreeableService.buildExcludeFilter(excludeM));
			}

			if (parentId == null && !asyncLoadAll) {
				models = baseTreeableService.findRootAndChild(searchFilters);
			} else {
				models = baseTreeableService.findAllWithSort(searchFilters);
			}
		}

		return baseTreeableService.convertToZtreeList(models, async && !asyncLoadAll && parentId != null, onlyCheckLeaf);
	}

	@RequestMapping(value = "ajax/{parent}/appendChild", method = RequestMethod.GET, produces = "application/json")
	@ResponseBody
	public Object ajaxAppendChild(HttpServletRequest request, @PathVariable("parent") M parent, @RequestParam(value = "name") String name) {
		if (permissions != null) {
			permissions.assertHasCreatePermission();
		}
		M child = newModel();
		child.setName(name);
		baseTreeableService.appendChild(parent, child);
		return baseTreeableService.convertToZtree(child, true, true);
	}

	@RequestMapping(value = "ajax/{id}/delete", method = RequestMethod.GET, produces = "application/json")
	@ResponseBody
	public Object ajaxDeleteSelfAndChildren(@PathVariable("id") ID id) {
		if (this.permissions != null) {
			this.permissions.assertHasEditPermission();
		}

		M tree = baseTreeableService.findById(id);
		baseTreeableService.deleteSelfAndChild(tree);
		return tree;
	}

	@RequestMapping(value = "ajax/{id}/rename", method = RequestMethod.GET, produces = "application/json")
	@ResponseBody
	public Object ajaxRename(HttpServletRequest request, @PathVariable("id") M tree, @RequestParam("newName") String newName) {
		if (permissions != null) {
			permissions.assertHasUpdatePermission();
		}
		tree.setName(newName);
		baseTreeableService.save(tree);
		return baseTreeableService.convertToZtree(tree, true, true);
	}

	@RequestMapping(value = "ajax/{sourceId}/{targetId}/{moveType}/move", method = RequestMethod.GET, produces = "application/json")
	@ResponseBody
	public Object ajaxMove(@PathVariable("sourceId") M source, @PathVariable("targetId") M target, @PathVariable("moveType") String moveType) {
		if (this.permissions != null) {
			this.permissions.assertHasEditPermission();
		}
		baseTreeableService.move(source, target, moveType);
		return source;
	}

	@RequestMapping("ajax/autocomplete")
	@PageableDefaults(value = 30)
	@ResponseBody
	public Set<String> autocomplete(HttpServletRequest request, @RequestParam("term") String term, @RequestParam(value = "excludeId", required = false) ID excludeId) {
		return baseTreeableService.findNames(SearchFilters.parse(request), PageRequest.of(0, 30), term, excludeId);
	}

	@RequestMapping(value = "success")
	public String success(Model model) {
		setCommonData(model);
		return commonViewName("success");
	}

	/**
	 * 所有RequestMapping方法调用前的Model准备方法, 实现Struts2
	 * Preparable二次部分绑定的效果,先根据form的id从数据库查出User对象,再把Form提交的内容绑定到该对象上。
	 * 因为仅update()方法的form中有id属性，因此仅在update时实际执行.
	 */
	@ModelAttribute
	public void getModel(@RequestParam(value = "id", required = false) ID id, Model model) {
		if (id != null) {
			model.addAttribute("m", baseTreeableService.findById(id));
		}
	}

	@Override
    protected void setCommonData(Model model) {
		super.setCommonData(model);
		if (permissions != null) {
			model.addAttribute("resourceIdentity", permissions.getResourceIdentity());
		}
		// 供页面显示
		model.addAttribute("entityName", entityName);
	}

	public void setEntityName(String entityName) {
		this.entityName = entityName;
	}

	public void setCommonViewPrefix(String commonViewPrefix) {
		this.commonViewPrefix = commonViewPrefix;
	}

	public void setListAlsoSetCommonData(boolean listAlsoSetCommonData) {
		this.listAlsoSetCommonData = listAlsoSetCommonData;
	}

}
